package org.budo.validation.aop.interceptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.validation.validator.factory.BudoValidatorFactory;

/**
 * @author lmw
 */
public abstract class AbstractBudoValidationMethodInterceptor implements MethodInterceptor {
    private static final String METHOD_PARAMETER_NAMES_NULL = "METHOD_PARAMETER_NAMES_NULL";

    private Map<Method, Annotation[][]> methodParameterAnnotations = new HashMap<Method, Annotation[][]>();

    private Map<Method, Object> methodParameterNames = new HashMap<Method, Object>();

    private BudoValidatorFactory budoValidatorFactory = new BudoValidatorFactory();

    protected abstract void validationMethodArgument(Integer parameterIndex, String parameterName, Object argumentValue, Annotation[] parameterAnnotations);

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method invocationMethod = invocation.getMethod();
        String[] methodParameterNames = this.getMethodParameterNames(invocationMethod);

        if (null == methodParameterNames) { // 有可能会取不到值
            return invocation.proceed();
        }

        Class<?> targetType = invocation.getThis().getClass();
        Annotation[][] methodParameterAnnotations = this.getMethodParameterAnnotations(targetType, invocationMethod);
        Object[] argumentValues = invocation.getArguments();

        // 逐个验证参数
        for (int index = 0; index < argumentValues.length; index++) {
            String parameterName = methodParameterNames[index];
            Object argumentValue = argumentValues[index];
            Annotation[] parameterAnnotations = methodParameterAnnotations.length > index ? methodParameterAnnotations[index] : new Annotation[0];
            this.validationMethodArgument(index, parameterName, argumentValue, parameterAnnotations); // 如果验证失败会走异常
        }

        // 验证成功
        return invocation.proceed();
    }

    private String[] getMethodParameterNames(Method invocationMethod) {
        Object paramNames = methodParameterNames.get(invocationMethod);
        if (null != paramNames && paramNames instanceof String[]) { // 缓存中的正常的值
            return (String[]) paramNames;
        }

        if (null != paramNames && paramNames.equals(METHOD_PARAMETER_NAMES_NULL)) { // 缓存起来的空值
            return null;
        }

        String[] parameterNames = ReflectUtil.getMethodParameterNames(invocationMethod);
        if (null != parameterNames) {
            methodParameterNames.put(invocationMethod, parameterNames); // 缓存正常值
        } else {
            methodParameterNames.put(invocationMethod, METHOD_PARAMETER_NAMES_NULL); // 缓存空值
        }

        return parameterNames;
    }

    private Annotation[][] getMethodParameterAnnotations(Class<?> targetType, Method method) {
        Annotation[][] annotations = methodParameterAnnotations.get(method);
        if (null != annotations) {
            return annotations;
        }

        Method interfaceMethod = this.findInterfaceMethod(targetType, method);
        Annotation[][] parameterAnnotations = null == interfaceMethod ? new Annotation[0][0] : interfaceMethod.getParameterAnnotations();
        methodParameterAnnotations.put(method, parameterAnnotations);

        return parameterAnnotations;
    }

    private Method findInterfaceMethod(Class<?> targetType, Method method) {
        Class<?>[] interfaces = targetType.getInterfaces();
        for (Class<?> interfaceType : interfaces) {
            try {
                Method interfaceMethod = interfaceType.getDeclaredMethod(method.getName(), method.getParameterTypes());
                return interfaceMethod;
            } catch (NoSuchMethodException e) {
                //
            }
        }

        return null;
    }

    public BudoValidatorFactory getBudoValidatorFactory() {
        return this.budoValidatorFactory;
    }

    public void setBudoValidatorFactory(BudoValidatorFactory budoValidatorFactory) {
        this.budoValidatorFactory = budoValidatorFactory;
    }
}